<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button id="btn">点击我</button>

    <script>
      // 防止按钮多次点击，
      let btn = document.getElementById("btn");

      // 第一次写的方法
      //   let isLock = false;
      //   btn.onclick = function () {
      //     if (isLock) {
      //     } else {
      //       isLock = true;
      //       console.log(1);
      //       setTimeout(function () {
      //         isLock = false;
      //         console.log("timeup");
      //       }, 2000);
      //     }
      //   };

      //   然而，这种方法并不是最优的，因为它依赖于全局变量 isLock，并且没有返回任何可以取消延迟操作的方法。
      //如果需要更高级的防多次点击功能，可以考虑使用 setTimeout 和 clearTimeout 函数来实现更灵活的控制，或者使用现代前端框架（如React、Vue等）
      //提供的节流和防抖库函数。

      // 第二种方法
      //   const delay = function (fn, time) {
      //     let isLock = false;
      //     return function () {
      //       if (isLock) {
      //         return;
      //       }
      //       isLock = true;
      //       fn.call();
      //       setTimeout(() => {
      //         isLock = false;
      //       }, time);
      //     };
      //   };

      /**      
这个 delay 函数是一个有效的防多次点击（节流）函数，它通过闭包来记住 isLock 状态，并在指定的延迟时间后执行传入的回调函数 fn。这个函数的设计有几个优点：

封装性：delay 函数将防多次点击的逻辑封装在一个函数内部，使得代码更加模块化和可重用。
灵活性：通过传入不同的回调函数 fn 和延迟时间 time，delay 函数可以用于多种不同的场景。
即时执行：当用户点击按钮时，回调函数 fn 会立即执行，而不是等待延迟时间结束。
解锁机制：使用 setTimeout 来设置一个定时器，在延迟时间结束后将 isLock 状态设置为 false，从而解锁按钮，允许下一次点击。

然而，还有一些可以改进的地方：
错误处理：在 fn.call() 中没有提供 this 上下文或参数，如果回调函数需要特定的 this 绑定或参数，需要修改 fn.call() 的使用方式。
日志输出：console.log("timeup") 应该被视为一个可选的日志输出，可能不需要在生产环境中输出。可以考虑将其作为一个可选的参数传递给 delay 函数。
内存泄漏：每次调用 delay 函数都会创建一个新的闭包和定时器。如果在短时间内频繁调用 delay，可能会创建大量的闭包和定时器，导致内存泄漏。为了避免这种情况，可以考虑使用一个全局的映射来跟踪和管理定时器。
取消机制：当前的实现没有提供取消延迟执行的能力。在某些情况下，用户可能希望取消之前的延迟执行请求。可以通过返回一个取消函数来实现这一点。

考虑到上述改进点，下面是一个优化后的 delay 函数实现：
*/
      //   const delay = function (fn, time, context) {
      //     if (typeof fn !== 'function') {
      //         throw new Error('Expected a function as the first argument.');
      //     }
      //     let id = null;
      //     const call = function () {
      //       if (id) {
      //         return;
      //       }
      //       // 解决内存泄漏问题
      //       // 解决错误处理
      //       fn.apply(context ?? this);
      //       id = setTimeout(() => {
      //             id=null
      //       }, time);
      //     };
      //     // 解决取消机制
      //     const cancel = function () {
      //       clearTimeout(id);
      //       id=null
      //     };

      //     return {
      //       call,
      //       cancel,
      //     };
      //   };

      const debounce = function (fn, time, context = this) {
        if (typeof fn !== "function") {
          throw new Error("Expected a function as the first argument.");
        }

        let id = null;

        const safeCall = function () {
          if (id) {
            return;
          }
          try {
            fn.apply(context);
            id = setTimeout(() => {
              cancel(); // 到时间了要清除之前的定时器
            }, time);
          } catch (error) {
            console.error(error);
            // 可以在这里添加更多的错误处理逻辑
          }
        };

        const cancel = function () {
          clearTimeout(id);
          id = null;
        };

        return {
          call: safeCall,
          cancel,
        };
      };
      // btn.onclick = delay(function () {
      //   console.log(1);
      // }, 3000).call;
      /**
      总结：
      1.第一次采用islock全局变量控制
      2.第二次采用闭包，把islock作为闭包变量
      3.第三次优化点：
        3.1.用计时器的id来判断是否已经执行过，避免多次点击
        3.2.用try catch来捕获错误，避免出错
        3.3.用cancel方法来取消延迟执行
        3.4.用context来控制this指向
        3.5.用safeCall来包装call方法，避免多次点击
     
      */
        
      function throttle(callback, timeout) {
        let pre = 0;
        return function () {
          // 计算时间差（当前时间-上次执行的时间），小于时间范围，不执行
          const now = Date.now();
          if (now - pre <= timeout) {
            return;
          }
          pre = now;
          callback.call();
        };
      }

      btn.onclick = throttle(function () {
        console.log(1);
      }, 3000);
    </script>
  </body>
</html>
